﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace System.Reflection.Private
{
    public static class InstanceMemberAccess
    {
        const BindingFlags _bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;

        public static void SetValue(this object @this, string name, object value)
        {
            if (@this == null) throw new ArgumentNullException("this");
            if (name == null) throw new ArgumentNullException("name");

            var pfi = PropertyOrFieldInfo.GetForType(@this.GetType(), name, _bindingFlags);
            pfi.SetValue(@this,value);

        }

        public static object GetValue(this object @this, string name)
        {
            if (@this == null) throw new ArgumentNullException("this");
            if (name == null) throw new ArgumentNullException("name");

            var pfi = PropertyOrFieldInfo.GetForType(@this.GetType(), name, _bindingFlags);
            return pfi.GetValue(@this);
        }

        public static T GetValue<T>(this object @this, string name)
        {
            object val = GetValue(@this, name);
            return (T)val;
        }

        static string GetMemberName<TSource, TField>(Expression<Func<TSource, TField>> memberExpr)
        {
            if (memberExpr==null)
                throw new ArgumentNullException("memberExpr");

            Expression body = memberExpr.Body;
            if (body is MemberExpression)
            {
                return (body as MemberExpression).Member.Name;
            }


            UnaryExpression unaryExpr = body as UnaryExpression;
            if (unaryExpr==null) throw new ArgumentOutOfRangeException("memberExpr", "Body is not Member or Unary Expression");

            var operand = unaryExpr.Operand as MemberExpression;
            if (operand ==null) throw new ArgumentOutOfRangeException("memberExpr", "Body.Operand is not MemberExpression");

            return operand.Member.Name;
        }

        public static void SetValue<TSource, TField>(this TSource @this, Expression<Func<TSource, TField>> memberExpr, TField value)
        {
            string name = GetMemberName(memberExpr);
            SetValue(@this, name, value);
        }
    }

    public static class StaticMemberAccess
    {
        const BindingFlags _bindingFlags = BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public;

        public static void SetValue(Type type, string name, object value)
        {
            if (type == null) throw new ArgumentNullException("type");
            if (name == null) throw new ArgumentNullException("name");

            var pfi = PropertyOrFieldInfo.GetForType(type, name, _bindingFlags);
            pfi.SetValue(null, value);

        }

        public static object GetValue(Type type, string name)
        {
            if (type == null) throw new ArgumentNullException("type");
            if (name == null) throw new ArgumentNullException("name");

            var pfi = PropertyOrFieldInfo.GetForType(type, name, _bindingFlags);
            return pfi.GetValue(null);
        }

        public static T GetValue<T>(Type type, string name)
        {
            object val = GetValue(type, name);
            return (T) val;
        }
    }

    public static class StaticMemberAccess<T>
    {
     
        public static void SetValue(string name, object value)
        {
            StaticMemberAccess.SetValue(typeof(T),name,value);

        }

        public static object GetValue(string name)
        {
            return StaticMemberAccess.GetValue(typeof(T), name);
        }

        public static TValue GetValue<TValue>(string name)
        {
            return (TValue)StaticMemberAccess.GetValue(typeof(T), name);
        }
    }

    class PropertyOrFieldInfo
    {
        readonly MemberInfo _memberInfo;

        public MemberInfo MemberInfo
        {
            get { return _memberInfo; }
        }

        public PropertyOrFieldInfo(PropertyInfo propertyInfo)
        {
            if (propertyInfo == null) throw new ArgumentNullException("propertyInfo");
            _memberInfo = propertyInfo;
        }

        public PropertyOrFieldInfo(FieldInfo fieldInfo)
        {
            if (fieldInfo == null) throw new ArgumentNullException("fieldInfo");
            _memberInfo = fieldInfo;
        }

        public static PropertyOrFieldInfo GetForType(IReflect t, string name, BindingFlags bindingFlags)
        {
            if (t == null) throw new ArgumentNullException("t");
            if (name == null) throw new ArgumentNullException("name");

            PropertyInfo prop = t.GetProperty(name, bindingFlags);
            if (prop != null) return new PropertyOrFieldInfo(prop);

            FieldInfo fld = t.GetField(name, bindingFlags);
            if (fld != null) return new PropertyOrFieldInfo(fld);

            throw new InvalidOperationException(String.Format("Member {0} not found for type {1} with {2}", name, t, bindingFlags));
        }

        public bool IsProperty
        {
            get { return _memberInfo is PropertyInfo; }
        }

        public void SetValue(object obj, object value)
        {
            var pi = _memberInfo as PropertyInfo;
            if (pi != null)
            {
                pi.SetValue(obj, value);
                return;
            }

            var fi = _memberInfo as FieldInfo;
            fi.SetValue(obj, value);
        }

        public object GetValue(object obj)
        {
            var pi = _memberInfo as PropertyInfo;
            if (pi != null) return pi.GetValue(obj);

            var fi = _memberInfo as FieldInfo;
            return fi.GetValue(obj);
        }

        public override string ToString()
        {
            string type = (IsProperty) ? "Property" : "Field";
            return String.Format("{0}:{1}", _memberInfo.Name, type);
        }


    }

 
}